/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.ncsu.csc573.project1.server;

import edu.ncsu.csc573.project1.util.Constants;
import edu.ncsu.csc573.project1.util.Constants.PSMethod;
import edu.ncsu.csc573.project1.util.Constants.Status;
import edu.ncsu.csc573.project1.util.PSRequest;
import edu.ncsu.csc573.project1.util.PSResponse;
import edu.ncsu.csc573.project1.util.PeerData;
import edu.ncsu.csc573.project1.util.RfcData;
import edu.ncsu.csc573.project1.util.RfcField;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 * @author svpendse1
 */
public class Server {

    LinkedList<PeerData> peerList;      //List of peers
    ConcurrentHashMap<PeerData, LinkedList<RfcData> > peerRfcMap;   //The peer->RFC map (thread-safe)
    ServerSocket serverSocket;      //The server socket
    Socket clientSocket;        //The client socket
    
    public Server() {
        peerRfcMap = new ConcurrentHashMap<PeerData, LinkedList<RfcData> >();
        peerList = new LinkedList<PeerData>();
        try {
            serverSocket = new ServerSocket(Constants.kPortNumber);
        } catch (IOException e) {
            System.err.println("Could not listen on port : " + Constants.kPortNumber);
        }
        System.out.println("Server ready");
        while (true) {
            try {
                clientSocket = serverSocket.accept();
                new Thread(new PeerHandler(clientSocket)).start();
            } catch (IOException e) {
                System.err.println("Accept failed");
            }
            
        }
    }
    class PeerHandler implements Runnable {
        Socket clientSocket;
        DataOutputStream out;
        DataInputStream in;
        PeerData currentPeer;
        boolean active;
        int uploadServerPortNumber; 
        PeerHandler(Socket clientSocket) {
            this.clientSocket = clientSocket;
            this.active = true;
            try {
                out = new DataOutputStream(this.clientSocket.getOutputStream());
                in = new DataInputStream(this.clientSocket.getInputStream());
                uploadServerPortNumber = in.readInt();
            } catch (IOException e) {
                System.err.println("Failed to create reader/writer objects for "
                                   + "the client");
            }
            System.out.println("Connected to peer : " + clientSocket.getInetAddress().getHostAddress());
            currentPeer = new PeerData(clientSocket.getInetAddress().getHostAddress(), uploadServerPortNumber);
            if (! peerList.contains(currentPeer)) {
                peerList.add(currentPeer);
            } else {
                System.err.println("Duplicate Peer");
            }
        }
        
        public void run() {
            while (active) {
                try {
                   respondToRequest();
                } catch (Exception e) {
                    e.printStackTrace();
//                    System.err.println("Error in reading data from socket : "); 
                }
            }
            System.out.println("Peer Handler Thread exiting");
        }
        
        private void respondToRequest() throws Exception {
            while (in.available() == 0) {
            }
            byte[] input = new byte[in.readInt()];
            in.readFully(input);
            String message = new String(input);
            PSRequest request = null;
            request = PSRequest.parseFromString(message); 
            if (request == null) { 
                //Bad Request
                System.out.println("Exception....!");
                PSResponse response = new PSResponse.PSResponseBuilder().status(Status.BAD_REQUEST)
                                                        .version(Constants.kVersion)
                                                        .responseList(null).build();
                out.writeInt(response.getResponseMessage().length());
                out.writeBytes(response.getResponseMessage());
                return;
            }

            System.out.println(request.getVersion());
            if (! request.getVersion().equals("P2P-CI/1.0")) {
                //Version not supported
                PSResponse response = new PSResponse.PSResponseBuilder().status(Status.P2P_CI_VERSION_NOT_SUPPORTED)
                                                        .version(Constants.kVersion)
                                                        .responseList(null).build();
                out.writeInt(response.getResponseMessage().length());
                out.writeBytes(response.getResponseMessage());
                return;
            }

            //System.out.print(request.getRequestMessage());
            if (request.getExchangeMethod() == PSMethod.ADD) {
                System.out.println("Adding a new RFC to the index");
                RfcData newRfc = new RfcData(request.getRfcNumber(), 
                                             request.getTitleField().getValue(), 
                                             currentPeer.getHostName());

                if (peerRfcMap.containsKey(currentPeer)) {
                    System.out.println("Updating exiting peer index");
                    LinkedList<RfcData> rfcList = peerRfcMap.get(currentPeer);
                    rfcList.add(newRfc);
                } else {
                    LinkedList<RfcData> rfcList = new LinkedList<RfcData>();
                    rfcList.add(newRfc);
                    peerRfcMap.put(currentPeer, rfcList);
                    System.out.println("Adding new peer to the index");
                }
            } else if (request.getExchangeMethod() == PSMethod.LOOKUP) {
                System.out.println("A lookup request from the peer");

                RfcData searchRfc = new RfcData(request.getRfcNumber(), 
                                                request.getTitleField().getValue(), "");
                
                LinkedList<PeerData> matchingPeers = new LinkedList<PeerData>();
                Iterator it = peerRfcMap.entrySet().iterator();
                while (it.hasNext()) {
                    @SuppressWarnings("unchecked")
                    Map.Entry<PeerData, LinkedList<RfcData> > pairs = 
                            (Map.Entry<PeerData, LinkedList<RfcData> >) it.next();
                    LinkedList<RfcData> currentRfcList = pairs.getValue(); 
                    for (int i = 0; i < currentRfcList.size(); ++i) {
                        if (currentRfcList.get(i).getNumber() == searchRfc.getNumber()) {
                            matchingPeers.add(pairs.getKey());
                        }
                        /*if (currentRfcList.get(i).getName().trim().equals(searchRfc.getName().trim())
                            && currentRfcList.get(i).getNumber() == searchRfc.getNumber()) {
                            matchingPeers.add(pairs.getKey());
                        }*/
                    }
                }
                
                if (! matchingPeers.isEmpty()) {
                    System.out.println("Found match");
                    LinkedList<RfcField> rfcFieldList = new LinkedList<RfcField>();

                    for (int i = 0; i < matchingPeers.size(); ++i) {
                       rfcFieldList.add(new RfcField(searchRfc.getNumber(), 
                                        searchRfc.getName(), 
                                        matchingPeers.get(i).getHostName(), 
                                        matchingPeers.get(i).getPortNumber())); 
                    }
                    PSResponse response = new PSResponse.PSResponseBuilder().status(Status.OK)
                                                        .version(Constants.kVersion)
                                                        .responseList(rfcFieldList).build();
                   
                    out.writeInt(response.getResponseMessage().length());
                    out.writeBytes(response.getResponseMessage());
                } else {
                    PSResponse response = new PSResponse.PSResponseBuilder()
                                              .status(Status.NOT_FOUND)
                                              .version(Constants.kVersion)
                                              .responseList(null).build();

                    out.writeInt(response.getResponseMessage().length());
                    out.writeBytes(response.getResponseMessage());
                }
            } else if (request.getExchangeMethod() == PSMethod.LIST) {
                System.out.println("A List request from the peer");
                
                Iterator it = peerRfcMap.entrySet().iterator();
                LinkedList<RfcField> rfcFieldList = new LinkedList<RfcField>();
                while (it.hasNext()) {
                    @SuppressWarnings("unchecked")
                    Map.Entry<PeerData, LinkedList<RfcData> > pairs = 
                            (Map.Entry<PeerData, LinkedList<RfcData> >) it.next();
                    PeerData currentPeer = pairs.getKey();
                    LinkedList<RfcData> currentRfcList = pairs.getValue();
                    for (int i = 0; i < currentRfcList.size(); ++i) {
                        
                        RfcField currentRfcField = 
                                new RfcField(currentRfcList.get(i).getNumber(), 
                                             currentRfcList.get(i).getName(), 
                                             currentPeer.getHostName(), 
                                             currentPeer.getPortNumber());
                        
                        rfcFieldList.add(currentRfcField);
                    }
                }
                
                if ( ! rfcFieldList.isEmpty()) {
                    PSResponse response = new PSResponse.PSResponseBuilder()
                                                        .status(Status.OK)
                                                        .version(Constants.kVersion)
                                                        .responseList(rfcFieldList).build();
                    out.writeInt(response.getResponseMessage().length());
                    out.writeBytes(response.getResponseMessage());
                } else {
                    PSResponse response = new PSResponse.PSResponseBuilder()
                                                        .status(Status.NOT_FOUND)
                                                        .version(Constants.kVersion)
                                                        .responseList(null).build();
                    out.writeInt(response.getResponseMessage().length());
                    out.writeBytes(response.getResponseMessage());

                }
            } else if (request.getExchangeMethod() == PSMethod.CLOSE) {
                System.out.println("Request to close connection");
                //PeerData peer = new PeerData(request.getHostField().getValue(), 
                //                             clientSocket.getPort());

                peerList.remove(currentPeer);
                peerRfcMap.remove(currentPeer);
                this.active = false;
                try {
                    in.close();
                    out.close();
                    clientSocket.close();
                    System.out.println("Connection closed. Resources freed");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }
    
    public static void main(String[] args) {
        Server server = new Server();
    }
}
